# Copyright 1999-2005 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

RC_GOT_FUNCTIONS="yes"

# Override defaults with user settings ...
[ -f /etc/conf.d/rc ] && source /etc/conf.d/rc

# daemontools dir
SVCDIR="/var/lib/supervise"

# Check /etc/conf.d/rc for a description of these ...
svclib="/lib/rcscripts"
svcdir=${svcdir:-/var/lib/init.d}
svcmount=${svcmount:-no}
svcfstype=${svcfstype:-tmpfs}
svcsize=${svcsize:-1024}

# Different types of dependencies
deptypes="need use"
# Different types of order deps
ordtypes="before after"

#
# Internal variables
#

# Dont output to stdout?
RC_QUIET_STDOUT="no"
RC_VERBOSE=${RC_VERBOSE:-no}

# Should we use color?
RC_NOCOLOR=${RC_NOCOLOR:-no}
# Can the terminal handle endcols?
RC_ENDCOL="yes"

#
# Default values for rc system
#
RC_TTY_NUMBER=${RC_TTY_NUMBER:-11}
RC_PARALLEL_STARTUP=${RC_PARALLEL_STARTUP:-no}
RC_NET_STRICT_CHECKING=${RC_NET_STRICT_CHECKING:-no}
RC_VOLUME_ORDER=${RC_VOLUME_ORDER:-raid evms lvm dm}
RC_USE_FSTAB=${RC_USE_FSTAB:-no}
RC_USE_CONFIG_PROFILE=${RC_USE_CONFIG_PROFILE:-yes}
RC_FORCE_AUTO=${RC_FORCE_AUTO:-no}
RC_DEVICES=${RC_DEVICES:-auto}

#
# Default values for e-message indentation and dots
#
RC_INDENTATION=''
RC_DEFAULT_INDENT=2
#RC_DOT_PATTERN=' .'
RC_DOT_PATTERN=''

# void import_addon(char *addon)
#
#  Import code from the specified addon if it exists
#
import_addon() {
	local addon=${svclib}/addons/$1
	if [[ -r ${addon} ]] ; then
		source "${addon}"
		return 0
	fi
	return 1
}

# void splash(...)
#
#  Notify bootsplash/splashutils/gensplash/whatever about
#  important events.
#
splash() {
	return 0
}
# This will override the splash() function...
if ! import_addon splash-functions.sh ; then
	[ -f /sbin/splash-functions.sh ] && source /sbin/splash-functions.sh
fi

# void profiling(...)
#
#  Notify bootsplash/whatever about important events.
#
profiling() {
	return 0
}
import_addon profiling-functions.sh

# void bootlog(...)
#
#  Notify bootlogger about important events.
bootlog() {
	return 0
}
[[ ${RC_BOOTLOG} == "yes" ]] && import_addon bootlogger.sh

# void get_bootconfig()
#
#    Get the BOOTLEVEL and SOFTLEVEL by setting
#    'bootlevel' and 'softlevel' via kernel
#    parameters.
#
get_bootconfig() {
	local copt=
	local newbootlevel=
	local newsoftlevel=

	if [[ -r /proc/cmdline ]] ; then
		for copt in $(</proc/cmdline) ; do
			case "${copt%=*}" in
				bootlevel)
					newbootlevel=${copt##*=}
					;;
				softlevel)
					newsoftlevel=${copt##*=}
					;;
			esac
		done
	fi

	if [[ -n ${newbootlevel} ]] ; then
		export BOOTLEVEL=${newbootlevel}
	else
		export BOOTLEVEL="boot"
	fi

	if [[ -n ${newsoftlevel} ]] ; then
		export DEFAULTLEVEL=${newsoftlevel}
	else
		export DEFAULTLEVEL="default"
	fi

	return 0
}

setup_defaultlevels() {
	get_bootconfig

	if get_bootparam "noconfigprofile" ; then
		export RC_USE_CONFIG_PROFILE="no"

	elif get_bootparam "configprofile" ; then
		export RC_USE_CONFIG_PROFILE="yes"
	fi

	if [ "${RC_USE_CONFIG_PROFILE}" = "yes" -a -n "${DEFAULTLEVEL}" ] && \
	   [ -d "/etc/runlevels/${BOOTLEVEL}.${DEFAULTLEVEL}" -o \
	     -L "/etc/runlevels/${BOOTLEVEL}.${DEFAULTLEVEL}" ]
	then
		export BOOTLEVEL="${BOOTLEVEL}.${DEFAULTLEVEL}"
	fi

	if [ -z "${SOFTLEVEL}" ] ; then
		if [ -f "${svcdir}/softlevel" ] ; then
			export SOFTLEVEL="$(< ${svcdir}/softlevel)"
		else
			export SOFTLEVEL="${BOOTLEVEL}"
		fi
	fi

	return 0
}

# void get_libdir(void)
#
#    prints the current libdir {lib,lib32,lib64}
#
get_libdir() {
	if [ -n "${CONF_LIBDIR_OVERRIDE}" ] ; then
		CONF_LIBDIR="${CONF_LIBDIR_OVERRIDE}"
	elif [ -x "/usr/bin/portageq" ] ; then
		CONF_LIBDIR="$(/usr/bin/portageq envvar CONF_LIBDIR)"
	fi
	echo ${CONF_LIBDIR:=lib}
}

# void esyslog(char* priority, char* tag, char* message)
#
#    use the system logger to log a message
#
esyslog() {
	local pri=
	local tag=

	if [ -x /usr/bin/logger ]
	then
		pri="$1"
		tag="$2"

		shift 2
		[[ -z "$*" ]] && return 0

		/usr/bin/logger -p "${pri}" -t "${tag}" -- "$*"
	fi

	return 0
}

# void eindent(int num)
#
#    increase the indent used for e-commands.
#
eindent() {
	local i=$1
	(( i > 0 )) || (( i = RC_DEFAULT_INDENT ))
	esetdent $(( ${#RC_INDENTATION} + i ))
}

# void eoutdent(int num)
#
#    decrease the indent used for e-commands.
#
eoutdent() {
	local i=$1
	(( i > 0 )) || (( i = RC_DEFAULT_INDENT ))
	esetdent $(( ${#RC_INDENTATION} - i ))
}

# void esetdent(int num)
#
#    hard set the indent used for e-commands.
#    num defaults to 0
#
esetdent() {
	local i=$1
	(( i < 0 )) && (( i = 0 ))
	RC_INDENTATION=$(printf "%${i}s" '')
}

# void einfo(char* message)
#
#    show an informative message (with a newline)
#
einfo() {
	einfon "$*\n"
	LAST_E_CMD=einfo
	return 0
}

# void einfon(char* message)
#
#    show an informative message (without a newline)
#
einfon() {
	[[ ${RC_QUIET_STDOUT} == yes ]] && return 0
	[[ ${RC_ENDCOL} != yes && ${LAST_E_CMD} == ebegin ]] && echo
	echo -ne " ${GOOD}*${NORMAL} ${RC_INDENTATION}$*"
	LAST_E_CMD=einfon
	return 0
}

# void ewarn(char* message)
#
#    show a warning message + log it
#
ewarn() {
	if [[ ${RC_QUIET_STDOUT} == yes ]]; then
		echo " $*"
	else
		[[ ${RC_ENDCOL} != yes && ${LAST_E_CMD} == ebegin ]] && echo
		echo -e " ${WARN}*${NORMAL} ${RC_INDENTATION}$*"
	fi

	# Log warnings to system log
	esyslog "daemon.warning" "rc-scripts" "$*"

	LAST_E_CMD=ewarn
	return 0
}

# void eerror(char* message)
#
#    show an error message + log it
#
eerror() {
	if [[ ${RC_QUIET_STDOUT} == yes ]]; then
		echo " $*" >/dev/stderr
	else
		[[ ${RC_ENDCOL} != yes && ${LAST_E_CMD} == ebegin ]] && echo
		echo -e " ${BAD}*${NORMAL} ${RC_INDENTATION}$*"
	fi

	# Log errors to system log
	esyslog "daemon.err" "rc-scripts" "$*"

	LAST_E_CMD=eerror
	return 0
}

# void ebegin(char* message)
#
#    show a message indicating the start of a process
#
ebegin() {
	local msg="$*" dots spaces=${RC_DOT_PATTERN//?/ }
	[[ ${RC_QUIET_STDOUT} == yes ]] && return 0

	if [[ -n ${RC_DOT_PATTERN} ]]; then
		dots=$(printf "%$(( COLS - 3 - ${#RC_INDENTATION} - ${#msg} - 7 ))s" '')
		dots=${dots//${spaces}/${RC_DOT_PATTERN}}
		msg="${msg}${dots}"
	else
		msg="${msg} ..."
	fi
	einfon "${msg}"
	[[ ${RC_ENDCOL} == yes ]] && echo

	LAST_E_LEN=$(( 3 + ${#RC_INDENTATION} + ${#msg} ))
	LAST_E_CMD=ebegin
	return 0
}

# void _eend(int error, char *efunc, char* errstr)
#
#    indicate the completion of process, called from eend/ewend
#    if error, show errstr via efunc
#
#    This function is private to functions.sh.  Do not call it from a
#    script.
#
_eend() {
	local retval=${1:-0} efunc=${2:-eerror} msg
	shift 2

	if [[ ${retval} == 0 ]]; then
		[[ ${RC_QUIET_STDOUT} == yes ]] && return 0
		msg="${BRACKET}[ ${GOOD}ok${BRACKET} ]${NORMAL}"
	else
		if [[ -c /dev/null ]]; then
			rc_splash "stop" &>/dev/null &
		else
			rc_splash "stop" &
		fi
		if [[ -n "$*" ]]; then
			${efunc} "$*"
		fi
		msg="${BRACKET}[ ${BAD}!!${BRACKET} ]${NORMAL}"
	fi

	if [[ ${RC_ENDCOL} == yes ]]; then
		echo -e "${ENDCOL}  ${msg}"
	else
		[[ ${LAST_E_CMD} == ebegin ]] || LAST_E_LEN=0
		printf "%$(( COLS - LAST_E_LEN - 6 ))s%b\n" '' "${msg}"
	fi

	return ${retval}
}

# void eend(int error, char* errstr)
#
#    indicate the completion of process
#    if error, show errstr via eerror
#
eend() {
	local retval=${1:-0}
	shift

	_eend ${retval} eerror "$*"

	LAST_E_CMD=eend
	return $retval
}

# void ewend(int error, char* errstr)
#
#    indicate the completion of process
#    if error, show errstr via ewarn
#
ewend() {
	local retval=${1:-0}
	shift

	_eend ${retval} ewarn "$*"

	LAST_E_CMD=ewend
	return $retval
}

# v-e-commands honor RC_VERBOSE which defaults to no.
# The condition is negated so the return value will be zero.
veinfo() { [[ "${RC_VERBOSE}" != yes ]] || einfo "$@"; }
veinfon() { [[ "${RC_VERBOSE}" != yes ]] || einfon "$@"; }
vewarn() { [[ "${RC_VERBOSE}" != yes ]] || ewarn "$@"; }
veerror() { [[ "${RC_VERBOSE}" != yes ]] || eerror "$@"; }
vebegin() { [[ "${RC_VERBOSE}" != yes ]] || ebegin "$@"; }
veend() {
	[[ "${RC_VERBOSE}" == yes ]] && { eend "$@"; return $?; }
	return ${1:-0}
}
veend() {
	[[ "${RC_VERBOSE}" == yes ]] && { ewend "$@"; return $?; }
	return ${1:-0}
}

# char *KV_major(string)
#
#    Return the Major (X of X.Y.Z) kernel version
#
KV_major() {
	[[ -z $1 ]] && return 1

	local KV=$@
	echo ${KV%%.*}
}

# char *KV_minor(string)
#
#    Return the Minor (Y of X.Y.Z) kernel version
#
KV_minor() {
	[[ -z $1 ]] && return 1

	local KV=$@
	KV=${KV#*.}
	echo ${KV%%.*}
}

# char *KV_micro(string)
#
#    Return the Micro (Z of X.Y.Z) kernel version.
#
KV_micro() {
	[[ -z $1 ]] && return 1

	local KV=$@
	KV=${KV#*.*.}
	echo ${KV%%[^[:digit:]]*}
}

# int KV_to_int(string)
#
#    Convert a string type kernel version (2.4.0) to an int (132096)
#    for easy compairing or versions ...
#
KV_to_int() {
	[[ -z $1 ]] && return 1

	local KV_MAJOR=$(KV_major "$1")
	local KV_MINOR=$(KV_minor "$1")
	local KV_MICRO=$(KV_micro "$1")
	local KV_int=$(( KV_MAJOR * 65536 + KV_MINOR * 256 + KV_MICRO ))

	# We make version 2.2.0 the minimum version we will handle as
	# a sanity check ... if its less, we fail ...
	if [[ ${KV_int} -ge 131584 ]] ; then
		echo "${KV_int}"
		return 0
	fi

	return 1
}

# int get_KV()
#
#    Return the kernel version (major, minor and micro concated) as an integer.
#    Assumes X and Y of X.Y.Z are numbers.  Also assumes that some leading
#    portion of Z is a number.
#    e.g. 2.4.25, 2.6.10, 2.6.4-rc3, 2.2.40-poop, 2.0.15+foo
#
get_KV() {
	local KV=$(uname -r)

	echo $(KV_to_int "${KV}")

	return $?
}

# bool get_bootparam(param)
#
#   return 0 if gentoo=param was passed to the kernel
#
#   EXAMPLE:  if get_bootparam "nodevfs" ; then ....
#
get_bootparam() {
	local x copt params retval=1

	[ ! -r "/proc/cmdline" ] && return 1

	for copt in $(< /proc/cmdline)
	do
		if [ "${copt%=*}" = "gentoo" ]
		then
			params="$(gawk -v PARAMS="${copt##*=}" '
				BEGIN {
					split(PARAMS, nodes, ",")
					for (x in nodes)
						print nodes[x]
				}')"

			# Parse gentoo option
			for x in ${params}
			do
				if [ "${x}" = "$1" ]
				then
#					echo "YES"
					retval=0
				fi
			done
		fi
	done

	return ${retval}
}

# Safer way to list the contents of a directory,
# as it do not have the "empty dir bug".
#
# char *dolisting(param)
#
#    print a list of the directory contents
#
#    NOTE: quote the params if they contain globs.
#          also, error checking is not that extensive ...
#
dolisting() {
	local x=
	local y=
	local tmpstr=
	local mylist=
	local mypath="$*"

	if [ "${mypath%/\*}" != "${mypath}" ]
	then
		mypath="${mypath%/\*}"
	fi

	for x in ${mypath}
	do
		[ ! -e "${x}" ] && continue

		if [ ! -d "${x}" ] && ( [ -L "${x}" -o -f "${x}" ] )
		then
			mylist="${mylist} $(ls "${x}" 2> /dev/null)"
		else
			[ "${x%/}" != "${x}" ] && x="${x%/}"

			cd "${x}"; tmpstr="$(ls)"

			for y in ${tmpstr}
			do
				mylist="${mylist} ${x}/${y}"
			done
		fi
	done

	echo "${mylist}"
}

# void save_options(char *option, char *optstring)
#
#    save the settings ("optstring") for "option"
#
save_options() {
	local myopts="$1"

	shift
	if [ ! -d "${svcdir}/options/${myservice}" ]
	then
		mkdir -p -m 0755 "${svcdir}/options/${myservice}"
	fi

	echo "$*" > "${svcdir}/options/${myservice}/${myopts}"

	return 0
}

# char *get_options(char *option)
#
#    get the "optstring" for "option" that was saved
#    by calling the save_options function
#
get_options() {
	if [ -f "${svcdir}/options/${myservice}/$1" ]
	then
		echo "$(< ${svcdir}/options/${myservice}/$1)"
	fi

	return 0
}

# char *add_suffix(char * configfile)
#
#    Returns a config file name with the softlevel suffix
#    appended to it.  For use with multi-config services.
add_suffix() {
	if [ "${RC_USE_CONFIG_PROFILE}" = "yes" -a -e "$1.${DEFAULTLEVEL}" ]
	then
		echo "$1.${DEFAULTLEVEL}"
	else
		echo "$1"
	fi

	return 0
}

# char *get_base_ver()
#
#    get the version of baselayout that this system is running
#
get_base_ver() {
	[[ ! -r /etc/gentoo-release ]] && return 0
	local ver=$(</etc/gentoo-release)
	echo ${ver##* }
}

# Network filesystems list for common use in rc-scripts.
# This variable is used in is_net_fs and other places such as
# localmount.
NET_FS_LIST="afs cifs coda davfs gfs ncpfs nfs nfs4 ocfs2 shfs smbfs"

# bool is_net_fs(path)
#
#   return 0 if path is the mountpoint of a networked filesystem
#
#   EXAMPLE:  if is_net_fs / ; then ...
#
is_net_fs() {
	local fstype
	# /proc/mounts is always accurate but may not always be available
	if [[ -e /proc/mounts ]]; then
		fstype=$( sed -n -e '/^rootfs/!s:.* '"$1"' \([^ ]*\).*:\1:p' /proc/mounts )
	else
		fstype=$( mount | sed -n -e 's:.* on '"$1"' type \([^ ]*\).*:\1:p' )
	fi
	[[ " ${NET_FS_LIST} " == *" ${fstype} "* ]]
	return $?
}

# bool is_uml_sys()
#
#   return 0 if the currently running system is User Mode Linux
#
#   EXAMPLE:  if is_uml_sys ; then ...
#
is_uml_sys() {
	grep -qs 'UML' /proc/cpuinfo
	return $?
}

# bool is_vserver_sys()
#
#   return 0 if the currently running system is a Linux VServer
#
#   EXAMPLE:  if is_vserver_sys ; then ...
#
is_vserver_sys() {
	grep -qs '^s_context:[[:space:]]*[1-9]' /proc/self/status
	return $?
}

# bool is_xenU_sys()
#
#   return 0 if the currently running system is an unprivileged Xen domain
#
#   EXAMPLE:  if is_xenU_sys ; then ...
#
is_xenU_sys() {
	[[ -d /proc/xen && ! -f /proc/xen/privcmd ]]
}

# bool get_mount_fstab(path)
#
#   return the parameters to pass to the mount command generated from fstab
#
#   EXAMPLE: cmd=$( get_mount_fstab /proc )
#            cmd=${cmd:--t proc none /proc}
#            mount -n ${cmd}
#
get_mount_fstab() {
	awk '$1 ~ "^#" { next }
	     $2 == "'$*'" { if (found++ == 0) { print "-t "$3,"-o "$4,$1,$2 } }
	     END { if (found > 1) { print "More than one entry for '$*' found in /etc/fstab!" > "/dev/stderr" } }
	' /etc/fstab
}

# char *reverse_list(list)
#
#   Returns the reversed order of list
#
reverse_list() {
	for (( i = $# ; i > 0 ; --i )); do
		echo -n "${!i} "
	done
}

# void start_addon(addon)
#
#   Starts addon.
#
start_addon() {
	local addon=$1
	(import_addon ${addon}-start.sh)
	return 0
}

# void start_volumes()
#
#   Starts all volumes in RC_VOLUME_ORDER.
#
start_volumes() {
	local x=

	for x in ${RC_VOLUME_ORDER}; do
		start_addon "${x}"
	done

	return 0
}

# void stop_addon(addon)
#
#   Stops addon.
#
stop_addon() {
	local addon=$1
	(import_addon ${addon}-stop.sh)
	return 0
}

# void stop_volumes()
#
#   Stops all volumes in RC_VOLUME_ORDER (reverse order).
#
stop_volumes() {
	local x=

	for x in $(reverse_list ${RC_VOLUME_ORDER}); do
		stop_addon "${x}"
	done

	return 0
}

# bool is_older_than(reference, files/dirs to check)
#
#   return 0 if any of the files/dirs are newer than
#   the reference file
#
#   EXAMPLE: if is_older_than a.out *.o ; then ...
is_older_than() {
	local x=
	local ref="$1"
	shift

	for x in "$@" ; do
		[[ ${x} -nt ${ref} ]] && return 0

		if [[ -d ${x} ]] ; then
			is_older_than "${ref}" "${x}"/* && return 0
		fi
	done

	return 1
}

# char* uniqify(char *arg, ...)
#
#   Ensure that params are unique
#
uniqify() {
    local result= x=
    while [[ -n "$1" ]] ; do
		[[ " ${result} " != *" $1 "* ]] && result="${result} $1"
		shift
	done
    echo "${result# *}"
}

##############################################################################
#                                                                            #
# This should be the last code in here, please add all functions above!!     #
#                                                                            #
# *** START LAST CODE ***                                                    #
#                                                                            #
##############################################################################

if [ -z "${EBUILD}" ] ; then
	# Setup a basic $PATH.  Just add system default to existing.
	# This should solve both /sbin and /usr/sbin not present when
	# doing 'su -c foo', or for something like:  PATH= rcscript start
	PATH="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/sbin:${PATH}"

	if [ "$(/sbin/consoletype 2> /dev/null)" = "serial" ] ; then
		# We do not want colors/endcols on serial terminals
		RC_NOCOLOR="yes"
		RC_ENDCOL="no"
	fi

	for arg in "$@" ; do
		case "${arg}" in
			# Lastly check if the user disabled it with --nocolor argument
			--nocolor|-nc)
				RC_NOCOLOR="yes"
				;;
		esac
	done

	if [ -r "/proc/cmdline" ] ; then
		setup_defaultlevels
	fi
else
	# Should we use colors ?
	if [[ $* != *depend* ]]; then
		# Check user pref in portage
		RC_NOCOLOR="$(portageq envvar NOCOLOR 2>/dev/null)"
		[ "${RC_NOCOLOR}" = "true" ] && RC_NOCOLOR="yes"
	else
		# We do not want colors during emerge depend
		RC_NOCOLOR="yes"
		# No output is seen during emerge depend, so this is not needed.
		RC_ENDCOL="no"
	fi
fi

if [[ -n ${EBUILD} && $* == *depend* ]]; then
	# We do not want stty to run during emerge depend
	COLS=80
else
	# Setup COLS and ENDCOL so eend can line up the [ ok ]
	COLS=${COLUMNS:-0}		# bash's internal COLUMNS variable
	(( COLS == 0 )) && COLS=$(stty size 2>/dev/null | cut -d' ' -f2)
	(( COLS > 0 )) || (( COLS = 80 ))	# width of [ ok ] == 7
fi

if [[ ${RC_ENDCOL} == yes ]]; then
	ENDCOL=$'\e[A\e['$(( COLS - 7 ))'G'
else
	ENDCOL=''
fi

# Setup the colors so our messages all look pretty
if [[ ${RC_NOCOLOR} == yes ]]; then
	unset GOOD WARN BAD NORMAL HILITE BRACKET
else
	GOOD=$'\e[32;01m'
	WARN=$'\e[33;01m'
	BAD=$'\e[31;01m'
	HILITE=$'\e[36;01m'
	BRACKET=$'\e[34;01m'
	NORMAL=$'\e[0m'
fi

##############################################################################
#                                                                            #
# *** END LAST CODE ***                                                      #
#                                                                            #
# This should be the last code in here, please add all functions above!!     #
#                                                                            #
##############################################################################


# vim:ts=4
